﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Web.Services.Protocols;
using System.Xml;
using ScrumTable.Common;
using ScrumTable.Common.Logging;
using ScrumTable.Common.Security;

#endregion

namespace ScrumTable.DL.Data.TFS.Services.SharePoint
{
    /// <summary>
    /// This class is used in order to connect to the Microsoft
    /// Windows SharePoint and its iteration list.
    /// </summary>
    /// <remarks>
    /// All operations on this instance will invoke the underlying WSS
    /// instance.
    /// </remarks>
    public class WssIterationList
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------
        
        private const int RequestTimeout = 20000; // ms
        private const string IterationListName = "ScrumTable_Iterations";
        private const string IterationListDescription = "Used for internal use of ScrumTable. Do not edit or change anything inside this list.";
        private const string ListServicePath = "_vti_bin/Lists.asmx"; // see <http://msdn.microsoft.com/en-us/library/lists.aspx>
        private const string NoErrorCode = "0x00000000";

        private static readonly XmlDocument ListConfiguration;

        private readonly ConnectionInformation _connectionCtx;

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Init immutable xml document.
        /// </summary>
        static WssIterationList()
        {
            // load configuration xml file with protocol information
            ListConfiguration = new XmlDocument();
            ListConfiguration.LoadXml(Properties.Resources.WssIterationList);
        }

        /// <summary>
        /// Creates a new WssIterationList instance. This constructor is used in
        /// the current library only.
        /// </summary>
        /// <param name="connectionCtx">
        ///     <attribute ctor="M:JetBrains.Annotations.NotNullAttribute.#ctor" />
        /// </param>
        internal WssIterationList(ConnectionInformation connectionCtx)
        {
            PreCondition.AssertNotNull(connectionCtx, "connectionCtx");

            _connectionCtx = connectionCtx;
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        /// <summary>
        /// Makes sure that the iteration list is available on the WSS side.
        /// </summary>
        /// <param name="wssUri">Specifies the sharepoint uri.</param>
        /// <returns>Returns the list version which is required to update the list.</returns>
        internal string Connect(Uri wssUri)
        {
            PreCondition.AssertNotNull(wssUri, "wssUri");

            string listVersion = null;

            using (Lists.Lists listsService = GetWebService(wssUri))
            {
                try
                {
                    // first try
                    XmlNode foundData = listsService.GetList(IterationListName);
                    listVersion = (foundData.Attributes["Version"] != null) ? foundData.Attributes["Version"].Value : null;

                    Logger<WssIterationList>.DebugFormat(Properties.Resources.LogIterationListFound, foundData.OuterXml);
                }
                catch (SoapException exc)
                {
                    Logger<WssIterationList>.Debug(Properties.Resources.LogIterationListNotFound, exc);

                    // second try
                    XmlNode addListData = listsService.AddList(
                        IterationListName,
                        IterationListDescription,
                        100 /*CustomList*/);
                    listVersion = addListData.Attributes["Version"].Value;

                    Logger<WssIterationList>.DebugFormat(Properties.Resources.LogIterationListCreated, addListData.OuterXml);

                    XmlNode updateListData = listsService.UpdateList(
                        IterationListName,
                        SelectConfigurationNode("/ListCommand/Create/ListProperties/List"),
                        SelectConfigurationNode("/ListCommand/Create/NewFields/Fields"),
                        SelectConfigurationNode("/ListCommand/Create/UpdateFields/Fields"),
                        SelectConfigurationNode("/ListCommand/Create/DeleteFields/Fields"),
                        listVersion);

                    Logger<WssIterationList>.DebugFormat(Properties.Resources.LogIterationListUpdated, updateListData.OuterXml);
                }
            }
            return listVersion;
        }

        /// <summary>
        /// Makes sure that the iteration list is available on the WSS side.
        /// </summary>
        /// <param name="wssUri">Specifies the sharepoint uri.</param>
        /// <param name="toUpdate">Iteration element to update.</param>
        /// <param name="listVersion">Specifies the list version to update.</param>
        /// <returns>Returns the list version which is required to update the list.</returns>
        internal void Update(Uri wssUri, string listVersion, WssIteration toUpdate)
        {
            PreCondition.AssertNotNull(wssUri, "wssUri");

            using (Lists.Lists listsService = GetWebService(wssUri))
            {
                XmlNode updateNode = SelectConfigurationNode("/ListCommand/Update/Batch");
                updateNode.Attributes["ListVersion"].Value = listVersion;

                // DateTime format: 2010-03-18T00:00:00Z
                SetNodeValue(updateNode, "Method/Field[@Name='ID']", toUpdate.WssId);
                SetNodeValue(updateNode, "Method/Field[@Name='StartDate']", toUpdate.StartDate.ToString("yyyy-MM-ddTHH:mm:ssZ"));
                SetNodeValue(updateNode, "Method/Field[@Name='EndDate']", toUpdate.EndDate.ToString("yyyy-MM-ddTHH:mm:ssZ"));
                SetNodeValue(updateNode, "Method/Field[@Name='TfsId']", toUpdate.TfsId.ToString(CultureInfo.InvariantCulture));
                SetNodeValue(updateNode, "Method/Field[@Name='Title']", toUpdate.IterationName ?? string.Empty);

                XmlNode resultData = listsService.UpdateListItems(IterationListName, updateNode);
                Logger<WssIterationList>.DebugFormat(Properties.Resources.LogIterationListItemUpdated, resultData.OuterXml);

                if (HasError(resultData))
                    throw new WssException(GetResultCode(resultData));
            }
        }


        /// <summary>
        /// Adds the given element to the underlying list.
        /// </summary>
        /// <param name="wssUri">Specifies the sharepoint uri.</param>
        /// <param name="toAdd">Iteration element to add.</param>
        /// <param name="listVersion">Specifies the list version to update.</param>
        /// <return>Returns the created id.</return>
        internal string Add(Uri wssUri, string listVersion, WssIteration toAdd)
        {
            PreCondition.AssertNotNull(wssUri, "wssUri");

            string wssIterationId = null;

            using (Lists.Lists listsService = GetWebService(wssUri))
            {
                XmlNode newNode = SelectConfigurationNode("/ListCommand/Add/Batch");
                newNode.Attributes["ListVersion"].Value = listVersion;
                
                // DateTime format: 2010-03-18T00:00:00Z
                SetNodeValue(newNode, "Method/Field[@Name='StartDate']", toAdd.StartDate.ToString("yyyy-MM-ddTHH:mm:ssZ"));
                SetNodeValue(newNode, "Method/Field[@Name='EndDate']", toAdd.EndDate.ToString("yyyy-MM-ddTHH:mm:ssZ"));
                SetNodeValue(newNode, "Method/Field[@Name='TfsId']", toAdd.TfsId.ToString(CultureInfo.InvariantCulture));
                SetNodeValue(newNode, "Method/Field[@Name='Title']", toAdd.IterationName ?? string.Empty);

                XmlNode resultData = listsService.UpdateListItems(IterationListName, newNode);
                Logger<WssIterationList>.DebugFormat(Properties.Resources.LogIterationListAdded, resultData.OuterXml);

                if (!HasError(resultData))
                {
                    XmlNamespaceManager nsmgr = new XmlNamespaceManager(resultData.OwnerDocument.NameTable);
                    nsmgr.AddNamespace("z", "#RowsetSchema");
                    XmlNode insertedRow = resultData.SelectSingleNode("//z:row", nsmgr);

                    if (insertedRow != null)
                        wssIterationId = TryGetAttributeValue(insertedRow, "ows_ID");

                    toAdd.WssId = wssIterationId;
                }
                else
                {
                    throw new WssException(GetResultCode(resultData));
                }
            }
            return wssIterationId;
        }

        /// <summary>
        /// Retreives all users from the underlying sharepoint dictionary indexed by TfsID.
        /// </summary>
        /// <param name="wssUri">Specifies the sharepoint uri.</param>
        internal IDictionary<int, WssIteration> Get(Uri wssUri)
        {
            PreCondition.AssertNotNull(wssUri, "wssUri");

            IDictionary<int, WssIteration> retreivedData = new Dictionary<int, WssIteration>(0);

            using (Lists.Lists listsService = GetWebService(wssUri))
            {
                XmlNode resultData = listsService.GetListItems(
                    IterationListName,
                    string.Empty,
                    null,
                    SelectConfigurationNode("/ListCommand/Get/ViewFields"),
                    null,
                    null,
                    null);
                Logger<WssIterationList>.DebugFormat(Properties.Resources.LogIterationListGetData, resultData.OuterXml);

                XmlNamespaceManager nsmgr = new XmlNamespaceManager(resultData.OwnerDocument.NameTable);
                nsmgr.AddNamespace("z", "#RowsetSchema");
                XmlNodeList elements = resultData.SelectNodes("//z:row", nsmgr);

                if (elements == null)
                    throw new WssException(string.Empty);

                retreivedData = new Dictionary<int, WssIteration>(elements.Count);

                foreach (XmlNode node in resultData.SelectNodes("//z:row", nsmgr))
                {
                    string id = TryGetAttributeValue(node, "ows_ID");
                    string tfsIdRaw = TryGetAttributeValue(node, "ows_TfsId");
                    string tfsMdxId = TryGetAttributeValue(node, "ows_TfsMdxId");
                    string title = TryGetAttributeValue(node, "ows_Title");
                    int tfsId;

                    if (!Int32.TryParse(tfsIdRaw, NumberStyles.Integer, CultureInfo.InvariantCulture, out tfsId))
                        continue;

                    string endDateRaw = TryGetAttributeValue(node, "ows_EndDate");
                    string startDateRaw = TryGetAttributeValue(node, "ows_StartDate");

                    DateTime endDate;
                    DateTime startDate;
                    DateTime.TryParse(endDateRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out endDate);
                    DateTime.TryParse(startDateRaw, CultureInfo.InvariantCulture, DateTimeStyles.None, out startDate);

                    retreivedData[tfsId] = new WssIteration(title, id, tfsId, tfsMdxId, startDate, endDate);
                }
            }
            return retreivedData;
        }

        private Lists.Lists GetWebService(Uri wssUri)
        {
            // for ssl see http://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.servercertificatevalidationcallback.aspx
            Lists.Lists webService = new Lists.Lists();
            webService.Url = wssUri.ReplacePath(ListServicePath).ToString();
            webService.Credentials = _connectionCtx.NetworkCredentials;
            webService.Timeout = RequestTimeout;
            return webService;
        }

        private XmlNode SelectConfigurationNode(string xPath)
        {
            return ListConfiguration.SelectSingleNode(xPath).CloneNode(true);
        }

        private bool HasError(XmlNode resultNode)
        {
            return (GetResultCode(resultNode) != NoErrorCode);
        }

        private string GetResultCode(XmlNode resultNode)
        {
            XmlNode errorNode = null;

            foreach (XmlNode childNode in resultNode.ChildNodes)
            {
                if (childNode.Name == "Result")
                {
                    errorNode = childNode.FirstChild;
                    break;
                }
            }

            if (errorNode != null)
            {
                return errorNode.FirstChild.Value;
            }
            return string.Empty;
        }

        private string TryGetAttributeValue(XmlNode toGetAttribute, string xmlAttributeName)
        {
            return (toGetAttribute.Attributes[xmlAttributeName] != null &&
                    toGetAttribute.Attributes[xmlAttributeName].Value != null)
                       ? toGetAttribute.Attributes[xmlAttributeName].Value
                       : string.Empty;
        }

        private void SetNodeValue(XmlNode parent, string xpath, string toSet)
        {
            parent.SelectSingleNode(xpath).AppendChild(parent.OwnerDocument.CreateTextNode(toSet));
        }

        #endregion

        #region Events
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}
